package com.fpc.cn.springbootutil.common;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @date 2025/4/29
 * @author fupengcheng
 * @describe 批量插入封装
 */
@Slf4j
public class DbBatchUtil<M, B> {
    private static final ThreadLocal<ThreadLocalType> DB_BATCH_LOCAL = new ThreadLocal<>();

    public static ThreadLocalType local() {
        return DB_BATCH_LOCAL.get();
    }

    public static void local(ThreadLocalType flag) {
        DB_BATCH_LOCAL.set(flag);
    }

    public static void remove() {
        DB_BATCH_LOCAL.remove();
    }

    public enum ThreadLocalType {
        批量操作
    }

    private static final Map<Class<?>, SerializedLambda> CLASS_LAMBDA_CACHE = new ConcurrentHashMap<>();

    public int batch(List<B> list, BatchInsert<M, B> fn) {
        SqlSessionFactory sessionFactory = ApplicationUtil.getBean(SqlSessionFactory.class);
        return batch(sessionFactory, list, fn, 500);
    }
    public int batch(List<B> list, BatchInsert<M, B> fn, int size) {
        SqlSessionFactory sessionFactory = ApplicationUtil.getBean(SqlSessionFactory.class);
        return batch(sessionFactory, list, fn, size);
    }
    public int batch(SqlSessionFactory sessionFactory, List<B> list, BatchInsert<M, B> fn, int size) {
        if(list == null || list.size() == 0) {
            return 0;
        }
        long s = System.currentTimeMillis();
        // 获取mybatis的会话
        SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
        try {
            DbBatchUtil.local(ThreadLocalType.批量操作);
            SerializedLambda lambda = lambda(fn);
            Class<?> mapperClass = Class.forName(lambda.getImplClass().replaceAll("/", "."));
            // 获取session中的getMapper方法。用于获取mapper。
            Method method = method(session.getClass(), "getMapper");
            Object invoke = method.invoke(session, mapperClass);
            String methodName = lambda.getImplMethodName();
            // 获取mapper中的方法
            method = method(invoke.getClass(), methodName);
            int a = 0;
            for (B t : list) {
                method.invoke(invoke, t);
                a++;
                if(a % size == 0) {
                    session.commit();
                }
            }
            session.commit();
            log.info("批量成功插入{}条数据，用时{}毫秒", list.size(), System.currentTimeMillis() - s);
            log.info("批量成功操作【{}】条数据，用时【{}】毫秒", list.size(), System.currentTimeMillis() - s);
            return list.size();
        } catch (Exception e) {
            session.rollback();
            log.info("批量插入异常，用时{}毫秒", System.currentTimeMillis() - s);
            log.info("批量操作异常，用时【{}】毫秒", System.currentTimeMillis() - s);
            throw new RuntimeException(e);
        } finally {
            if(session != null) {
                try {
                    session.close();
                } catch (Exception ee) {
                    log.error("session关闭报错", ee);
                }
            }
            DbBatchUtil.remove();
        }
    }

    private Method method(Class<?> clazz, String methodName) {
        Method[] methods = clazz.getMethods();
        for(Method m : methods) {
            if(methodName.equals(m.getName())) {
                return m;
            }
        }
        throw new RuntimeException("【" + clazz + "】找不到【" + methodName + "】方法");
    }


    @FunctionalInterface
    public interface BatchInsert<M, B> extends Serializable {
        void get(M source, B p);
    }

    /**
     * 根据lambda获取方法名
     * @param fn lambda表达式
     * @return SerializedLambda
     */
    private SerializedLambda lambda(BatchInsert<M, B> fn) {
        SerializedLambda lambda = CLASS_LAMBDA_CACHE.get(fn.getClass());
        if (lambda == null) {
            try {
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
                CLASS_LAMBDA_CACHE.put(fn.getClass(), lambda);
            } catch (Exception e) {
                log.error("lambda获取异常", e);
            }
        }
        return lambda;
    }
}

